package com.itbaizhan.shopping_search_service.service;

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.query_dsl.*;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.CompletionSuggestOption;
import co.elastic.clients.elasticsearch.core.search.FieldSuggester;
import co.elastic.clients.elasticsearch.core.search.Suggester;
import co.elastic.clients.elasticsearch.core.search.Suggestion;
import co.elastic.clients.elasticsearch.indices.AnalyzeRequest;
import co.elastic.clients.elasticsearch.indices.AnalyzeResponse;
import co.elastic.clients.elasticsearch.indices.analyze.AnalyzeToken;
import co.elastic.clients.elasticsearch.ml.AnalysisConfig;
import co.elastic.clients.json.JsonData;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.itbaizhan.shopping_common.pojo.*;

import com.itbaizhan.shopping_common.service.SearchService;

import com.itbaizhan.shopping_search_service.repository.GoodsESRepository;
import lombok.SneakyThrows;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.elasticsearch.client.elc.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.client.elc.NativeQuery;
import org.springframework.data.elasticsearch.client.elc.NativeQueryBuilder;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.*;

/**
 * 搜索服务实现类
 */
@DubboService
@Service
public class SearchServiceImpl implements SearchService {
	
	@Autowired
	private ElasticsearchClient client;
	@Autowired
	private GoodsESRepository goodsESRepository;
	@Autowired
	private ElasticsearchTemplate template;
	
	/**
	 * 分词方法
	 *
	 * @param text
	 * @param analyzer
	 * @return
	 */
	@SneakyThrows//忽略异常
	public List<String> analyze(String text, String analyzer) {
		
		// 分词请求
		AnalyzeRequest request = AnalyzeRequest.of(a ->
				a.index("goods").analyzer(analyzer).text(text)
		);
		//发送分词请求
		AnalyzeResponse response = client.indices().analyze(request);
		//处理分词结果
		ArrayList<String> works = new ArrayList<>();
		List<AnalyzeToken> tokens = response.tokens();
		for (AnalyzeToken token : tokens) {
			String term = token.token();
			works.add(term);
		}
		return works;
	}
	
	/**
	 * 自动补全关键字
	 *
	 * @param keyword 被补齐的词
	 * @return
	 */
	@SneakyThrows
	public List<String> autoSuggest(String keyword) {
		//1.创建自动补齐查询条件
		Suggester suggester = Suggester.of(
				s -> s.suggesters("prefix_suggestion", FieldSuggester.of(
						fs -> fs.completion(
								cs -> cs.skipDuplicates(true)
										.size(10)
										.field("tags")
						
						)
				)).text(keyword)
		);
		
		//2.自动补齐查询
		SearchResponse<Map> response = client.search(s -> s.index("goods")
				.suggest(suggester), Map.class);
		
		//处理查询结果
		Map resultMap = response.suggest();
		List<Suggestion> suggestionList = (List) resultMap.get("prefix_suggestion");
		Suggestion suggestion = suggestionList.get(0);
		List<CompletionSuggestOption> resultList = suggestion.completion().options();
		
		List<String> result = new ArrayList<>();
		for (CompletionSuggestOption completionSuggestOption : resultList) {
			String text = completionSuggestOption.text();
			result.add(text);
		}
		return result;
	}
	
	
	/**
	 * 同步商品到ES
	 *
	 * @param goodsDesc 商品详情
	 */
	public void syncGoodsToES(GoodsDesc goodsDesc) {
		// 将商品详情对象转为GoodsES对象
		GoodsES goodsES = new GoodsES();
		goodsES.setId(goodsDesc.getId());
		goodsES.setGoodsName(goodsDesc.getGoodsName());
		goodsES.setCaption(goodsDesc.getCaption());
		goodsES.setPrice(goodsDesc.getPrice());
		goodsES.setHeaderPic(goodsDesc.getHeaderPic());
		goodsES.setBrand(goodsDesc.getBrand().getName());
		//存入商品类型集合
		List<String> productType = new ArrayList<>();
		productType.add(goodsDesc.getProductType1().getName());
		productType.add(goodsDesc.getProductType2().getName());
		productType.add(goodsDesc.getProductType3().getName());
		goodsES.setProductType(productType);
		//创建规格集合对象
		Map<String, List<String>> map = new HashMap<>();
		// 遍历规格
		for (Specification specification : goodsDesc.getSpecifications()) {
			// 规格项名集合
			List<String> values = new ArrayList<>();
			// 遍历规格项集合
			for (SpecificationOption specificationOption : specification.getSpecificationOptions()) {
				values.add(specificationOption.getOptionName());
			}
			
			map.put(specification.getSpecName(), values);
		}
		// 存入规格集合
		goodsES.setSpecification(map);
		
		// new关键字集合
		List<String> tags = new ArrayList();
		tags.add(goodsDesc.getBrand().getName()); //品牌名是关键字
		tags.addAll(analyze(goodsDesc.getGoodsName(), "ik_smart"));//商品名分词后为关键词
		tags.addAll(analyze(goodsDesc.getCaption(), "ik_smart"));//副标题分词后为关键词
		//存入关键字集合
		goodsES.setTags(tags);
		
		// 构建GoodsES对象完成，将GoodsES对象存入ES
		goodsESRepository.save(goodsES);
		
	}
	
	/**
	 * 删除商品
	 *
	 * @param id 商品id
	 */
	public void delete(Long id) {
		goodsESRepository.deleteById(id);
	}
	
	/**
	 * 搜索goods
	 *
	 * @param goodsSearchParam 搜索条件
	 * @return
	 */
	public GoodsSearchResult search(GoodsSearchParam goodsSearchParam) {
		//1.构造ES搜索条件
		NativeQuery nativeQuery = buildQuery(goodsSearchParam);//调用buildQuery方法
		
		//2.搜索
		SearchHits<GoodsES> search = template.search(nativeQuery, GoodsES.class);
		
		//3.将查询结果封装为mp的page页面对象
		//3.1 将SearchHits对象转为List
		List<GoodsES> content = new ArrayList<>();
		for (SearchHit<GoodsES> hit : search) {
			GoodsES goodsES = hit.getContent();
			content.add(goodsES);
		}
		//3.2 封装page对象
		Page<GoodsES> goodsPage = new Page();
		goodsPage.setCurrent(goodsSearchParam.getPage())//当前页
				.setSize(goodsSearchParam.getSize())//每页条数
				.setTotal(search.getTotalHits())//总条数
				.setRecords(content);//商品集合结果集
		
		
		//4.封装查询结果
		GoodsSearchResult goodsSearchResult = new GoodsSearchResult();
		//4.1 封装商品
		goodsSearchResult.setGoodsPage(goodsPage);
		//4.2 封装查询参数
		goodsSearchResult.setGoodsSearchParam(goodsSearchParam);
		//4.3封装查询面板
		buildSearchPanel(goodsSearchParam, goodsSearchResult);//调用buildSearchPanel方法
		//返回结果
		return goodsSearchResult;
	}
	
	/**
	 * 构造搜索条件
	 *
	 * @param goodsSearchParam 查询条件对象
	 * @return
	 */
	private NativeQuery buildQuery(GoodsSearchParam goodsSearchParam) {
		//1.创建复杂条件对象
		NativeQueryBuilder nativeQueryBuilder = new NativeQueryBuilder();
		BoolQuery.Builder builder = new BoolQuery.Builder();
		
		//2.如果查询条件有关键词，匹配商品名，副标题，品牌，没有则查询所有商品
		if (!StringUtils.hasText(goodsSearchParam.getKeyword())) {
			//查询所有商品
			MatchAllQuery matchQuery = new MatchAllQuery.Builder().build();
			builder.must(matchQuery._toQuery());
		} else {
			String keyword = goodsSearchParam.getKeyword();
			MultiMatchQuery keywordQuery = MultiMatchQuery.of(
					q -> q.query(keyword).fields("goodsName", "caption", "brand"));
			builder.must(keywordQuery._toQuery());
		}
		
		//3.如果查询条件有品牌，根据品牌查询商品
		String brand = goodsSearchParam.getBrand();
		if (StringUtils.hasText(brand)) {
			TermQuery brandQuery = TermQuery.of(
					q -> q.field("brand").value(brand));
			builder.must(brandQuery._toQuery());
		}
		
		//4.如果查询条件有价格区间，根据价格区间查询商品
		Double highPrice = goodsSearchParam.getHighPrice();//最高价
		Double lowPrice = goodsSearchParam.getLowPrice();//最低价
		if (highPrice != null && highPrice != 0) {
			RangeQuery lte = RangeQuery.of(q -> q.field("price").lte(JsonData.of(highPrice)));
			builder.must(lte._toQuery());
		}
		if (lowPrice != null && lowPrice != 0) {
			RangeQuery gte = RangeQuery.of(q -> q.field("price").gte(JsonData.of(lowPrice)));
			builder.must(gte._toQuery());
		}
		
		//5.如果查询条件有规格选项，根据规格选项查询商品
		Map<String, String> specificationOption = goodsSearchParam.getSpecificationOption();
		if (specificationOption != null && specificationOption.size() > 0) {
			Set<Map.Entry<String, String>> entries = specificationOption.entrySet();
			for (Map.Entry<String, String> entry : entries) {
				String key = entry.getKey();//规格名
				String value = entry.getValue();//规格值
				if (StringUtils.hasText(key)) {
					TermQuery termQuery = TermQuery.of(
							q -> q.field("specification." + key + ".keyword").value(value));
					builder.must(termQuery._toQuery());
				}
				
			}
		}
		
		//给搜索条件对象添加查询条件对象
		nativeQueryBuilder.withQuery(builder.build()._toQuery());
		
		//6.添加分页条件
		PageRequest pageRequest = PageRequest.of(goodsSearchParam.getPage() - 1, goodsSearchParam.getSize());
		nativeQueryBuilder.withPageable(pageRequest);
		
		//7.如果查询条件有排序，添加排序条件
		String sortFiled = goodsSearchParam.getSortFiled();//排序字段新品NEW或 价格PRICE
		String sort = goodsSearchParam.getSort();//排序方式 升序ASC或降序DESC
		if (StringUtils.hasText(sortFiled) && StringUtils.hasText(sort)) {
			Sort sortParam = null;
			// 新品的正序是id的倒序
			if (sortFiled.equals("NEW")) {
				if (sort.equals("ASC")) {
					sortParam = Sort.by(Sort.Direction.DESC, "id");
				}
				if (sort.equals("DESC")) {
					sortParam = Sort.by(Sort.Direction.ASC, "id");
				}
			}
			if (sortFiled.equals("PRICE")) {
				if (sort.equals("ASC")) {
					sortParam = Sort.by(Sort.Direction.ASC, "price");
				}
				if (sort.equals("DESC")) {
					sortParam = Sort.by(Sort.Direction.DESC, "price");
				}
				
			}
			nativeQueryBuilder.withSort(sortParam);
		}
		//8.返回条件对象[构建复杂条件对象]
		return nativeQueryBuilder.build();
	}
	
	/**
	 * 封装查询面板，封装查询面板，即根据查询条件，找到查询结果关联度前20名的商品进行封装
	 *
	 * @param goodsSearchParam
	 * @param goodsSearchResult
	 */
	public void buildSearchPanel(GoodsSearchParam goodsSearchParam, GoodsSearchResult goodsSearchResult) {
		// 1.构造搜索条件
		goodsSearchParam.setPage(1);
		goodsSearchParam.setSize(20);
		goodsSearchParam.setSort(null);
		goodsSearchParam.setSortFiled(null);
		NativeQuery query = buildQuery(goodsSearchParam);
		// 2.搜索
		SearchHits<GoodsES> search = template.search(query, GoodsES.class);
		// 3.将结果封装为List对象
		List<GoodsES> content = new ArrayList();
		for (SearchHit<GoodsES> goodsESSearchHit : search) {
			GoodsES goodsES = goodsESSearchHit.getContent();
			content.add(goodsES);
		}
		// 4.遍历集合，封装查询面板
		// 商品相关的品牌列表
		Set<String> brands = new HashSet();
		// 商品相关的类型列表
		Set<String> productTypes = new HashSet();
		// 商品相关的规格列表
		Map<String, Set<String>> specifications = new HashMap();
		for (GoodsES goodsES : content) {
			// 获取品牌
			brands.add(goodsES.getBrand());
			// 获取类型
			List<String> productType = goodsES.getProductType();
			productTypes.addAll(productType);
			// 获取规格
			Map<String, List<String>> specification = goodsES.getSpecification();
			Set<Map.Entry<String, List<String>>> entries = specification.entrySet();
			for (Map.Entry<String, List<String>> entry : entries) {
				// 规格名
				String key = entry.getKey();
				// 规格值
				List<String> value = entry.getValue();
				// 如果没有遍历出该规格，新增键值对，如果已经遍历出该规格，则向规格中添加规格项
				if (!specifications.containsKey(key)) {
					specifications.put(key, new HashSet(value));
				} else {
					specifications.get(key).addAll(value);
				}
			}
		}
		goodsSearchResult.setBrands(brands);
		goodsSearchResult.setProductType(productTypes);
		goodsSearchResult.setSpecifications(specifications);
	}
	
}
